package provider

import (
	"errors"
	"log/slog"
	"quipus/internal/utils"
	v1pb "quipus/proto/gen/api/v1"
	"strings"

	"google.golang.org/protobuf/proto"
)

type DictUnit struct {
	ShowName    string `yaml:"show_name"`
	Description string `yaml:"description"`
}

type SpecDict struct {
	Dict map[string]DictUnit `yaml:"dict"`
}

var (
	specs map[string]*v1pb.LLMProviderSpec
	dicts map[string]*SpecDict
)

func init() {
	specs = make(map[string]*v1pb.LLMProviderSpec)
	dicts = make(map[string]*SpecDict)
	Register()
}

// Register adds a scheme setup function to the list.
func Register() error {
	providerPath := "etc/llm-provider/provider"
	dictPath := "etc/llm-provider/dict"

	providerFiles := utils.ListFile(providerPath)
	if providerFiles == nil {
		return errors.New("no provider file found")
	}

	for _, v := range providerFiles {
		filePath := providerPath + "/" + v
		pds := &v1pb.LLMProviderSpec{}
		err := utils.ReadPBYamlConfig(filePath, pds)
		if err != nil {
			slog.Error("read config error", slog.Any("error", err))
			continue
		}
		specs[pds.Name] = pds
	}

	dictFiles := utils.ListFile(dictPath)
	if dictFiles == nil {
		slog.Error("no dict file found")
	}

	for _, v := range dictFiles {
		dictPath := dictPath + "/" + v
		d := &SpecDict{}
		err := utils.ReadYamlConfig(dictPath, d)
		if err != nil {
			slog.Error("read config error", slog.Any("error", err))
			continue
		}

		v = strings.Trim(v, ".yaml")
		dicts[v] = d
	}
	return nil
}

func getShowInfo(key string, pSd *SpecDict, gSd *SpecDict) (string, string) {
	if pSd != nil && pSd.Dict != nil {
		pMap := pSd.Dict
		v, ok := pMap[key]
		if ok {
			return v.ShowName, v.Description
		} else {
			if gSd != nil && gSd.Dict != nil {
				gMap := gSd.Dict
				v, ok := gMap[key]
				if ok {
					return v.ShowName, v.Description
				} else {
					return "", ""
				}
			}
		}
	} else {
		if gSd != nil && gSd.Dict != nil {
			gMap := gSd.Dict
			v, ok := gMap[key]
			if ok {
				return v.ShowName, v.Description
			} else {
				return "", ""
			}
		}
	}

	return "", ""
}

func BuildProviderSpecs(spec *v1pb.LLMProviderSpec, locale string, model string) error {
	gSd, ok := dicts[locale]
	if !ok {
		slog.Info("no global dict found")
	}

	dictKey := spec.Name + "_" + locale
	pSd, ok := dicts[dictKey]
	if !ok {
		slog.Info("no provider dict found", slog.Any("dictKey", dictKey))

	}

	var modelMap bool = false
	if spec.Models != nil {
		for _, v := range spec.Models {
			if model != "" && v.ModelName != model {
				continue
			}

			showName, desc := getShowInfo(v.ModelName, pSd, gSd)
			v.ShowName = showName
			v.Description = desc
			if v.ModelParams != nil {
				for _, vv := range v.ModelParams {
					showName, desc := getShowInfo(vv.Variable, pSd, gSd)
					vv.ShowName = showName
					vv.Description = desc
				}
			}

			modelMap = true
		}
	}

	showName, _ := getShowInfo("name", pSd, gSd)
	spec.ShowName = showName
	if spec.ProviderConfig != nil {
		for _, v := range spec.ProviderConfig {
			showName, desc := getShowInfo(v.Variable, pSd, gSd)
			v.ShowName = showName
			v.Description = desc
		}
	}

	if spec.AuthConfig != nil {
		for _, v := range spec.AuthConfig {
			showName, desc := getShowInfo(v.Variable, pSd, gSd)
			v.ShowName = showName
			v.Description = desc
		}
	}

	if spec.AssistantConfig != nil {
		for _, v := range spec.AssistantConfig {
			showName, desc := getShowInfo(v.Variable, pSd, gSd)
			v.ShowName = showName
			v.Description = desc
		}
	}

	if spec.ModelConfig != nil {
		for _, v := range spec.ModelConfig {
			showName, desc := getShowInfo(v.Variable, pSd, gSd)
			v.ShowName = showName
			v.Description = desc
		}
	}

	if model != "" && modelMap != true {
		return errors.New("no model found")

	}
	return nil
}

func ListProviderSpecs(provider string, model string, locale string) ([]*v1pb.LLMProviderSpec, error) {
	var lps []*v1pb.LLMProviderSpec
	if provider == "" {
		for _, v := range specs {
			p := proto.Clone(v).(*v1pb.LLMProviderSpec)
			BuildProviderSpecs(p, locale, model)
			lps = append(lps, p)
		}
	} else {
		ps, ok := specs[provider]
		if ok {
			p := proto.Clone(ps).(*v1pb.LLMProviderSpec)
			BuildProviderSpecs(p, locale, model)
			lps = append(lps, p)
		} else {
			slog.Info("no provider found", slog.Any("provider", provider))
			return nil, errors.New("no provider found")
		}
	}

	return lps, nil
}
